package org.vison.wonfu.framework.dao.spi;

import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.vison.wonfu.framework.config.utils.MultiLevelConfigProvider;
import org.vison.wonfu.framework.dao.AbstractMapper;
import org.vison.wonfu.framework.dao.Column;
import org.vison.wonfu.framework.dao.Constans;
import org.vison.wonfu.framework.dao.IPO;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

public class EntityDefineChecker implements CommandLineRunner {

    private static Logger logger = LoggerFactory.getLogger(EntityDefineChecker.class);

    private MultiLevelConfigProvider multiLevelConfigProvider;

    private SqlSessionFactory sqlSessionFactory;

    private static final String RESOURCE_PATTERN = "/**/*.class";


    @Override
    public void run(String... args) throws Exception {
        List<String> basePackages = getMapperPackages();
        if (CollectionUtils.isEmpty(basePackages)) {
            return;
        }
        if (!checkEntityDefine()) {
            logger.info("已关闭实体定义检查!");
            return;
        }
        List<String> errorMessages = new ArrayList<>();
        for (String basePackage : basePackages) {
            Set<Class<?>> classes = scanIPOClasses(basePackage);
            for (Class<?> clazz : classes) {
                if (IPO.class.isAssignableFrom(clazz)) {
                    validateIPOClass(clazz, errorMessages);
                }
            }
        }
        if (!errorMessages.isEmpty()) {
            throw new RuntimeException(String.join("\n", errorMessages));
        }
    }

    /**
     * 验证IPO实现类的String属性是否符合要求
     */
    public static void validateIPOClass(Class<?> clazz, List<String> errorMessages) {
        // 获取所有字段（包括父类）
        List<Field> fields = getAllFields(clazz);

        for (Field field : fields) {
            Column columnAnnotation = field.getAnnotation(Column.class);
            if(columnAnnotation == null) {
                errorMessages.add(String.format("类 %s 的属性 %s 未添加@Column注解",
                        clazz.getName(), field.getName()));
                continue;
            }
            // 只检查String类型的属性
            if (field.getType().equals(String.class)) {
                // 检查是否有@Column注解
               if (columnAnnotation.length() <= 0) {
                    errorMessages.add(String.format("类 %s 的属性 %s 的@Column注解未指定有效的length值（当前值: %d）",
                            clazz.getName(), field.getName(), columnAnnotation.length()));
                }
            }
            if(!field.getName().equals(Constans.ENTITY_PRIMARY_COLUMN_NAME)) {
                String comment = columnAnnotation.comment();
                if(comment == null) {
                    errorMessages.add(String.format("类 %s 的属性 %s 未添加@Comment注解", clazz.getName(), field.getName()));
                }else if(comment.isEmpty()) {
                    errorMessages.add(String.format("类 %s 的属性 %s @Comment注解的value值为空", clazz.getName(), field.getName()));
                }
            }
        }
    }

    /**
     * 获取类及其所有父类的字段
     */
    private static List<Field> getAllFields(Class<?> clazz) {
        List<Field> fields = new ArrayList<>();
        Class<?> currentClass = clazz;

        // 遍历所有父类，直到Object
        while (currentClass != null && !currentClass.equals(Object.class)) {
            Field[] declaredFields = currentClass.getDeclaredFields();
            for (Field field : declaredFields) {
                fields.add(field);
            }
            currentClass = currentClass.getSuperclass();
        }

        return fields;
    }


    private Set<Class<?>> scanIPOClasses(String basePackage) throws IOException, ClassNotFoundException {
        Set<Class<?>> mapperInterfaces = new HashSet<>();
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                ClassUtils.convertClassNameToResourcePath(basePackage) + RESOURCE_PATTERN;

        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);

        for (org.springframework.core.io.Resource resource : resolver.getResources(packageSearchPath)) {
            if (resource.isReadable()) {
                MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                String className = metadataReader.getClassMetadata().getClassName();
                Class<?> clazz = Class.forName(className);

                // 检查是否实现了IPO接口
                if (clazz.isInterface() && isAssignableFromAbstractMapper(clazz)) {
                    mapperInterfaces.add(clazz);
                }
            }
        }
        Set<Class<?>> poClasses = extractPOClasses(mapperInterfaces);

        return poClasses;
    }

    private Set<Class<?>> extractPOClasses(Set<Class<?>> mapperInterfaces) {
        Set<Class<?>> poClasses = new HashSet<>();

        for (Class<?> mapperInterface : mapperInterfaces) {
            // 获取接口的泛型父接口
            Type[] genericInterfaces = mapperInterface.getGenericInterfaces();
            for (Type genericInterface : genericInterfaces) {
                if (genericInterface instanceof ParameterizedType) {
                    ParameterizedType parameterizedType = (ParameterizedType) genericInterface;
                    Type rawType = parameterizedType.getRawType();

                    // 检查是否是AbstractMapper的参数化类型
                    if (rawType == AbstractMapper.class) {
                        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                        if (actualTypeArguments.length == 1 && actualTypeArguments[0] instanceof Class) {
                            Class<?> poClass = (Class<?>) actualTypeArguments[0];
                            poClasses.add(poClass);
                        }
                    }
                }
            }
        }
        return poClasses;
    }

    /**
     * 判断接口是否继承了AbstractMapper
     */
    private boolean isAssignableFromAbstractMapper(Class<?> clazz) {
        for (Class<?> iface : clazz.getInterfaces()) {
            if (iface == AbstractMapper.class) {
                return true;
            }
            if (isAssignableFromAbstractMapper(iface)) {
                return true;
            }
        }
        return false;
    }

    public EntityDefineChecker(MultiLevelConfigProvider multiLevelConfigProvider, SqlSessionFactory sqlSessionFactory) {
            this.multiLevelConfigProvider = multiLevelConfigProvider;
            this.sqlSessionFactory = sqlSessionFactory;
    }

    private Boolean checkEntityDefine() {
        return multiLevelConfigProvider.getBoolean(Constans.ENTITY_DEFINE_CHECKER,true);
    }


    public List<String> getMapperPackages() {
        Configuration configuration = sqlSessionFactory.getConfiguration();
        Collection<Class<?>> mapperInterfaces = configuration.getMapperRegistry().getMappers();

        List<String> packages = new ArrayList<>();
        for (Class<?> mapperInterface : mapperInterfaces) {
            String packageName = mapperInterface.getPackage().getName();
            if (!packages.contains(packageName)) {
                packages.add(packageName);
            }
        }

        return packages;
    }
}
